/*
* 文件：FollowControl.cs
* 作者：王阳
* 时间：2019-5-20
* 描述：
*/
using CSharpX;
using System;
using System.Collections;
using UnityEngine;
using UnityEngine.EventSystems;

namespace UnityX
{
    public class FollowControl : MonoBehaviour
    {
        public enum MouseKey
        {
            左键 = 0,
            右键 = 1,
            中键 = 2,
        }

        //目标位置
        [SerializeField]
        private Transform m_Target;
        [SerializeField]
        private Vector3 m_TargetPosition;
        private Vector3 m_TargetPositionSmooth;
        private Vector3 m_TargetPositionSmoothVelocity = Vector3.zero;

        [SerializeField]
        private float m_TargetPositionSmoothTime = -1;
        public Transform target
        {
            get => m_Target;
            set
            {
                m_Target = value;
                if (m_Target != null)
                {
                    if (m_TargetPositionSmoothTime < 0.001f)
                    {
                        m_TargetPosition = m_Target.position;
                        m_TargetPositionSmooth = m_TargetPosition;
                    }
                }
            }
        }
        public Vector3 targetPosition
        {
            get => m_TargetPosition;
            set
            {
                m_TargetPosition = value;
                if (m_Target != null)
                {
                    m_Target.position = m_TargetPosition;
                }
                if (m_TargetPositionSmoothTime < 0.001f)
                {
                    m_TargetPositionSmooth = m_TargetPosition;
                }
            }
        }
        public float targetPositionSmoothTime
        {
            get => m_TargetPositionSmoothTime; set
            {
                m_TargetPositionSmoothTime = value;
                if (m_TargetPositionSmoothTime < 0.001f)
                {
                    m_TargetPositionSmoothVelocity = Vector3.zero;
                }
            }
        }

        //俯仰角度
        [SerializeField]
        private float m_AngleX = 30;
        private float m_AngleXSmooth = 30;
        private float m_AngleXSmoothVelocity = 0;
        [SerializeField]
        private float m_AngleXMin = 5;
        [SerializeField]
        private float m_AngleXMax = 85;
        [SerializeField]
        private float m_AngleXSmoothTime = -1;
        public float angleX
        {
            get => m_AngleX;
            set
            {
                m_AngleX = value;
                if (m_AngleXSmoothTime < 0.001f)
                {
                    m_AngleXSmooth = m_AngleX;
                }
            }
        }
        public float angleXMin { get => m_AngleXMin; set => m_AngleXMin = value; }
        public float angleXMax { get => m_AngleXMax; set => m_AngleXMax = value; }
        public float angleXSmoothTime
        {
            get => m_AngleXSmoothTime;
            set
            {
                m_AngleXSmoothTime = value;
                if (m_AngleXSmoothTime < 0.001f)
                {
                    m_AngleXSmoothVelocity = 0;
                }
            }
        }

        //左右角度
        [SerializeField]
        private float m_AngleY = 0;
        private float m_AngleYSmooth = 0;
        private float m_AngleYSmoothVelocity = 0;
        //[SerializeField]
        //private float m_AngleYMin = -720;
        //[SerializeField]
        //private float m_AngleYMax = 720;
        [SerializeField]
        private float m_AngleYSmoothTime = -1;
        //[SerializeField]
        //private bool m_AngleFollow = false;//是否跟着目标转动
        public float angleY
        {
            get => m_AngleY;
            set
            {
                m_AngleY = value;
                if (m_AngleYSmoothTime < 0.001f)
                {
                    m_AngleYSmooth = m_AngleY;
                }
            }
        }
        //public float angleYMin { get => m_AngleYMin; set => m_AngleYMin = value; }
        //public float angleYMax { get => m_AngleYMax; set => m_AngleYMax = value; }
        public float angleYSmoothTime
        {
            get => m_AngleYSmoothTime;
            set
            {
                m_AngleYSmoothTime = value;
                if (m_AngleYSmoothTime < 0.001f)
                {
                    m_AngleYSmoothVelocity = 0;
                }
            }
        }


        //public bool angleFollow { get => m_AngleFollow; set => m_AngleFollow = value; }

        //跟随距离
        [SerializeField]
        private float m_Distance = 3;
        private float m_DistanceSmooth = 3;
        private float m_DistanceSmoothVelocity = 0;
        [SerializeField]
        private float m_DistanceMin = 1;
        [SerializeField]
        private float m_DistanceMax = 10;
        [SerializeField]
        private float m_DistanceSmoothTime = -1;
        public float distance
        {
            get => m_Distance;
            set
            {
                m_Distance = value;
                if (m_DistanceSmoothTime < 0.001f)
                {
                    m_DistanceSmooth = m_Distance;
                }
            }
        }
        public float distanceMin { get => m_DistanceMin; set => m_DistanceMin = value; }
        public float distanceMax { get => m_DistanceMax; set => m_DistanceMax = value; }
        public float distanceSmoothTime
        {
            get => m_DistanceSmoothTime;
            set
            {
                m_DistanceSmoothTime = value;
                if (m_DistanceSmoothTime < 0.001f)
                {
                    m_DistanceSmoothVelocity = 0;
                }
            }
        }

        //键盘输入控制
        [SerializeField]
        private bool m_KeyboardControl = false;//
        [SerializeField]
        private float m_KeyboardControlAngleXSpeed = 20;
        [SerializeField]
        private float m_KeyboardControlAngleYSpeed = 30;
        [SerializeField]
        private float m_KeyboardControlDistanceSpeed = 2;
        [SerializeField]
        private KeyCode m_UpKey = KeyCode.UpArrow;
        [SerializeField]
        private KeyCode m_DownKey = KeyCode.DownArrow;
        [SerializeField]
        private KeyCode m_LeftKey = KeyCode.LeftArrow;
        [SerializeField]
        private KeyCode m_RightKey = KeyCode.RightArrow;
        [SerializeField]
        private KeyCode m_FrontKey = KeyCode.KeypadMinus;
        [SerializeField]
        private KeyCode m_BackKey = KeyCode.KeypadPlus;
        public bool keyboardControl { get => m_KeyboardControl; set => m_KeyboardControl = value; }
        public float keyboardControlAngleXSpeed { get => m_KeyboardControlAngleXSpeed; set => m_KeyboardControlAngleXSpeed = value; }
        public float keyboardControlAngleYSpeed { get => m_KeyboardControlAngleYSpeed; set => m_KeyboardControlAngleYSpeed = value; }
        public float keyboardControlDistanceSpeed { get => m_KeyboardControlDistanceSpeed; set => m_KeyboardControlDistanceSpeed = value; }

        //鼠标输入控制
        [SerializeField]
        private bool m_MouseControl = false;//
        [SerializeField]
        private MouseKey m_MouseKey = MouseKey.左键;//
        [SerializeField]
        private float m_MouseControlAngleXSpeed = 90;
        [SerializeField]
        private float m_MouseControlAngleYSpeed = 180;
        [SerializeField]
        private float m_MouseControlDistanceSpeed = 0.5f;
        private bool mouseControling = false;
        private Vector3 lastMousePosition = Vector3.one;
        public bool mouseControl
        {
            get => m_MouseControl;
            set
            {
                m_MouseControl = value;
                if (!m_MouseControl)
                {
                    mouseControling = false;
                }
            }
        }
        public float mouseControlAngleXSpeed { get => m_MouseControlAngleXSpeed; set => m_MouseControlAngleXSpeed = value; }
        public float mouseControlAngleYSpeed { get => m_MouseControlAngleYSpeed; set => m_MouseControlAngleYSpeed = value; }
        public float mouseControlDistanceSpeed { get => m_MouseControlDistanceSpeed; set => m_MouseControlDistanceSpeed = value; }

        //触摸输入控制
        [SerializeField]
        private bool m_TouchControl = false;//
        [SerializeField]
        private float m_TouchControlAngleXSpeed = 120;
        [SerializeField]
        private float m_TouchControlAngleYSpeed = 240;
        [SerializeField]
        private float m_TouchControlDistanceSpeed = 10;
        private int touchControlCount = 0;
        private Vector3[] touchPosition = new Vector3[10];
        private int[] touchFID = new int[10];
        public bool touchControl { get => m_TouchControl; set => m_TouchControl = value; }
        public float touchControlAngleXSpeed { get => m_TouchControlAngleXSpeed; set => m_TouchControlAngleXSpeed = value; }
        public float touchControlAngleYSpeed { get => m_TouchControlAngleYSpeed; set => m_TouchControlAngleYSpeed = value; }
        public float touchControlDistanceSpeed { get => m_TouchControlDistanceSpeed; set => m_TouchControlDistanceSpeed = value; }

        //屏幕可控区域
        [SerializeField]
        private Rect m_ScreenArea = new Rect(0, 0, -1, -1);
        public Rect screenArea { get => m_ScreenArea; set => m_ScreenArea = value; }
        //点击到UI上检测
        [SerializeField]
        private bool m_UIClickCheck = false;//
        public bool uiClickCheck { get => m_UIClickCheck; set => m_UIClickCheck = value; }

        //距离控制拉近减速比
        [Range(0.001f, 1)]
        [SerializeField]
        private float m_DistanceSpeedChangeRatio = 0.2f;
        public float distanceSpeedChangeRatio { get => m_DistanceSpeedChangeRatio; set => m_DistanceSpeedChangeRatio = value; }

        //前面被遮挡检测
        [SerializeField]
        private bool m_OccludeCheck = false;//
        public bool occludeCheck { get => m_OccludeCheck; set => m_OccludeCheck = value; }

        private Camera m_Camera;

        private void Start()
        {
            m_TargetPositionSmooth = m_TargetPosition;
            m_AngleXSmooth = m_AngleX;
            m_AngleYSmooth = m_AngleY;
            m_DistanceSmooth = m_Distance;

            m_Camera = GetComponentInChildren<Camera>();
        }
        private void Update()
        {
            TouchInput();
            MouseInput();
            KeyboardInput();
        }
        private void LateUpdate()
        {
            UpdateTransform();
            OccludeCheck();
        }
        private void OnApplicationFocus(bool focus)
        {
            if (!focus)
            {
                mouseControling = false;
                touchControlCount = 0;
            }
        }
        private float GetDistanceSpeed(float speed)
        {
            return speed * Curve.L1(m_DistanceSpeedChangeRatio, 1, m_DistanceMin, m_DistanceMax, m_Distance);
        }
        private void KeyboardInput()
        {
            if (!m_KeyboardControl) return;

            if (Input.GetKey(m_UpKey))
            {
                m_AngleX += m_KeyboardControlAngleXSpeed * Time.deltaTime;
            }
            if (Input.GetKey(m_DownKey))
            {
                m_AngleX -= m_KeyboardControlAngleXSpeed * Time.deltaTime;
            }
            if (Input.GetKey(m_LeftKey))
            {
                m_AngleY += m_KeyboardControlAngleYSpeed * Time.deltaTime;
            }
            if (Input.GetKey(m_RightKey))
            {
                m_AngleY -= m_KeyboardControlAngleYSpeed * Time.deltaTime;
            }
            if (Input.GetKey(m_FrontKey))
            {
                m_Distance -= GetDistanceSpeed(m_KeyboardControlDistanceSpeed) * Time.deltaTime;
            }
            if (Input.GetKey(m_BackKey))
            {
                m_Distance += GetDistanceSpeed(m_KeyboardControlDistanceSpeed) * Time.deltaTime;
            }
        }
        private void MouseInput()
        {
            if (!m_MouseControl) return;
            if (touchControlCount > 0) return;

            if (Input.GetMouseButtonDown((int)m_MouseKey))
            {
                if (m_ScreenArea.width > 0 && !m_ScreenArea.Contains(Input.mousePosition))
                    return;
                if (m_UIClickCheck)
                {
                    if (!EventSystem.current.IsPointerOverGameObject())
                    {
                        mouseControling = true;
                        lastMousePosition = Input.mousePosition;
                    }
                }
                else
                {
                    mouseControling = true;
                    lastMousePosition = Input.mousePosition;
                }
            }
            else if (Input.GetMouseButtonUp((int)m_MouseKey))
            {
                mouseControling = false;
            }

            if (mouseControling)
            {
                Vector2 mousePositionDelta = Input.mousePosition - lastMousePosition;
                lastMousePosition = Input.mousePosition;
                float xSpeed = m_MouseControlAngleXSpeed / Screen.height;
                float ySpeed = m_MouseControlAngleYSpeed / Screen.width;
                m_AngleX -= mousePositionDelta.y * xSpeed;
                m_AngleY += mousePositionDelta.x * ySpeed;
            }

            if (Math.Abs(Input.mouseScrollDelta.y) > 0)
            {
                if (m_ScreenArea.width > 0 && !m_ScreenArea.Contains(Input.mousePosition))
                    return;
                if (m_UIClickCheck)
                {
                    if (!EventSystem.current.IsPointerOverGameObject())
                    {
                        m_Distance += -Input.mouseScrollDelta.y * GetDistanceSpeed(m_MouseControlDistanceSpeed);
                    }
                }
                else
                {
                    m_Distance += -Input.mouseScrollDelta.y * GetDistanceSpeed(m_MouseControlDistanceSpeed);
                }
            }
        }
        private void TouchInput()
        {
            if (!m_TouchControl) return;
            if (mouseControling) return;

            switch (touchControlCount)
            {
                case 0:
                    {
                        foreach (Touch touch in Input.touches)
                        {
                            if (touch.phase == TouchPhase.Began)
                            {
                                if (m_UIClickCheck && EventSystem.current.IsPointerOverGameObject(touch.fingerId))
                                    continue;
                                if (m_ScreenArea.width > 0 && !m_ScreenArea.Contains(touch.position))
                                    continue;
                                touchFID[touchControlCount] = touch.fingerId;
                                touchPosition[touchControlCount] = touch.position;
                                touchControlCount++;
                            }
                        }
                    }
                    break;
                case 1:
                    {
                        foreach (Touch touch in Input.touches)
                        {
                            if (touch.phase == TouchPhase.Began)
                            {
                                if (m_UIClickCheck && EventSystem.current.IsPointerOverGameObject(touch.fingerId))
                                    continue;
                                if (m_ScreenArea.width > 0 && !m_ScreenArea.Contains(touch.position))
                                    continue;
                                touchFID[touchControlCount] = touch.fingerId;
                                touchPosition[touchControlCount] = touch.position;
                                touchControlCount++;
                                return;
                            }
                        }
                        foreach (Touch touch in Input.touches)
                        {
                            if (touch.fingerId == touchFID[0])
                            {
                                float xSpeed = m_MouseControlAngleXSpeed / Screen.height;
                                float ySpeed = m_MouseControlAngleYSpeed / Screen.width;
                                m_AngleX -= touch.deltaPosition.y * xSpeed;
                                m_AngleY += touch.deltaPosition.x * ySpeed;
                                return;
                            }
                        }
                        touchControlCount = 0;
                    }
                    break;
                case 2:
                    {
                        Vector3 p1 = Vector3.zero;
                        Vector3 p2 = Vector3.zero;
                        int pCount = 0;
                        foreach (Touch touch in Input.touches)
                        {
                            if (touch.fingerId == touchFID[0])
                            {
                                p1 = touch.position;
                                pCount++;
                                break;
                            }
                        }
                        foreach (Touch touch in Input.touches)
                        {
                            if (touch.fingerId == touchFID[1])
                            {
                                p2 = touch.position;
                                pCount++;
                                break;
                            }
                        }
                        if (pCount == 2)
                        {
                            float v1 = Vector3.Distance(touchPosition[0], touchPosition[1]);
                            float v2 = Vector3.Distance(p1, p2);
                            float distanceSpeed = m_TouchControlDistanceSpeed / (Screen.width + Screen.height);
                            touchPosition[0] = p1;
                            touchPosition[1] = p2;
                            m_Distance += (v1 - v2) * GetDistanceSpeed(distanceSpeed);
                        }
                        else
                        {
                            touchControlCount = 0;
                        }
                    }
                    break;
                default:
                    touchControlCount = 0;
                    break;
            }
        }

        //更新Transform
        private void UpdateTransform()
        {
            if (m_Target != null)
            {
                m_TargetPosition = m_Target.position;
            }

            if (m_AngleX < m_AngleXMin) m_AngleX = m_AngleXMin;
            if (m_AngleX > m_AngleXMax) m_AngleX = m_AngleXMax;

            //if (m_AngleY < m_AngleYMin) m_AngleY = m_AngleYMin;
            //if (m_AngleY > m_AngleYMax) m_AngleY = m_AngleYMax;

            if (m_Distance < m_DistanceMin) m_Distance = m_DistanceMin;
            if (m_Distance > m_DistanceMax) m_Distance = m_DistanceMax;

            Vector3 position = Vector3.zero;
            Quaternion rotation = Quaternion.identity;

            if (m_TargetPositionSmoothTime < 0.001f)
            {
                m_TargetPositionSmooth = m_TargetPosition;
            }
            else
            {
                m_TargetPositionSmooth = Vector3.SmoothDamp(m_TargetPositionSmooth, m_TargetPosition, ref m_TargetPositionSmoothVelocity, m_TargetPositionSmoothTime);
            }
            if (m_AngleXSmoothTime < 0.001f)
            {
                m_AngleXSmooth = m_AngleX;
            }
            else
            {
                m_AngleXSmooth = Mathf.SmoothDamp(m_AngleXSmooth, m_AngleX, ref m_AngleXSmoothVelocity, m_AngleXSmoothTime);
            }
            if (m_AngleYSmoothTime < 0.001f)
            {
                m_AngleYSmooth = m_AngleY;
            }
            else
            {
                m_AngleYSmooth = Mathf.SmoothDamp(m_AngleYSmooth, m_AngleY, ref m_AngleYSmoothVelocity, m_AngleYSmoothTime);
            }
            if (m_DistanceSmoothTime < 0.001f)
            {
                m_DistanceSmooth = m_Distance;
            }
            else
            {
                m_DistanceSmooth = Mathf.SmoothDamp(m_DistanceSmooth, m_Distance, ref m_DistanceSmoothVelocity, m_DistanceSmoothTime);
            }

            rotation = Quaternion.Euler(m_AngleXSmooth, m_AngleYSmooth, 0);
            if (m_Camera != null && m_Camera.orthographic)
            {
                m_Camera.orthographicSize = m_DistanceSmooth * 0.5f;
                position = m_TargetPositionSmooth + (rotation * Vector3.forward * -m_DistanceMax);
                transform.SetPositionAndRotation(position, rotation);
            }
            else
            {
                position = m_TargetPositionSmooth + (rotation * Vector3.forward * -m_DistanceSmooth);
                transform.SetPositionAndRotation(position, rotation);
            }
        }

        //相机挡住检测
        private void OccludeCheck()
        {
            if (!m_OccludeCheck) return;

            RaycastHit hitInfo;
            if (Physics.Raycast(m_TargetPosition, transform.position - m_TargetPosition, out hitInfo, m_Distance, ~(1 << 18), QueryTriggerInteraction.Ignore))
            {
                transform.position = m_TargetPosition + (transform.rotation * Vector3.forward * -hitInfo.distance);
            }
        }

        #region 短暂平滑辅助扩展功能
        IEnumerator m_TemporaryEnumerator = null;
        float m_TemporaryTargetPositionSmoothSpeed = -1;
        float m_TemporaryAngleXSmoothSpeed = -1;
        float m_TemporaryAngleYSmoothSpeed = -1;
        float m_TemporaryDistanceSmoothSpeed = -1;
        /// <summary>
        /// 短暂平滑
        /// </summary>
        /// <param name="speed"></param>
        /// <param name="duration"></param>
        public void TemporarySmooth(float speed = 10.0f, float duration = 1.0f)
        {
            if (m_TemporaryEnumerator != null)
            {
                StopCoroutine(m_TemporaryEnumerator);
            }
            else
            {
                m_TemporaryTargetPositionSmoothSpeed = m_TargetPositionSmoothTime;
                m_TemporaryAngleXSmoothSpeed = m_AngleXSmoothTime;
                m_TemporaryAngleYSmoothSpeed = m_AngleYSmoothTime;
                m_TemporaryDistanceSmoothSpeed = m_DistanceSmoothTime;
            }
            m_TemporaryEnumerator = TemporarySmoothStart(speed, duration);
            StartCoroutine(m_TemporaryEnumerator);
        }
        private IEnumerator TemporarySmoothStart(float speed = 10.0f, float duration = 1.0f)
        {
            m_TargetPositionSmoothTime = speed;
            m_AngleXSmoothTime = speed;
            m_AngleYSmoothTime = speed;
            m_DistanceSmoothTime = speed;
            yield return duration;
            m_TargetPositionSmoothTime = m_TemporaryTargetPositionSmoothSpeed;
            m_AngleXSmoothTime = m_TemporaryAngleXSmoothSpeed;
            m_AngleYSmoothTime = m_TemporaryAngleYSmoothSpeed;
            m_DistanceSmoothTime = m_TemporaryDistanceSmoothSpeed;

            m_TemporaryEnumerator = null;
        }
        #endregion
    }
}